Telegram Group & Telegram Channel
🐍 Задача с подвохом: Декораторы и изменяемые объекты

Условие:

Что выведет следующий код и почему?

def memoize(fn):
cache = {}
def wrapper(arg):
if arg in cache:
print("Из кэша")
return cache[arg]
else:
result = fn(arg)
cache[arg] = result
return result
return wrapper

@memoize
def add_to_list(val, lst=[]):
lst.append(val)
return lst

res1 = add_to_list(1)
res2 = add_to_list(2)
res3 = add_to_list(1)

print(res1)
print(res2)
print(res3)

Вопрос:
Что именно выведется? В чём здесь двойная ловушка?

🔍 Анализ:

Сначала кажется, что:

1. add_to_list(1) вернёт [1].
2. add_to_list(2) вернёт [2].
3. add_to_list(1) либо вызовет функцию снова, либо вернёт результат из кэша.

Но есть два подвоха:

Подвох №1: изменяемый аргумент по умолчанию

Аргумент lst=[] создаётся один раз при определении функции. Все вызовы без передачи списка будут использовать один и тот же список.

Подвох №2: кэширование по ключу

Декоратор memoize сохраняет результат в кэше по ключу arg. Но функция возвращает список, который изменяется при каждом вызове. Даже если результат берётся из кэша, вы получите ссылку на тот же список, который менялся между вызовами!

🧮 Что реально произойдёт:

- `res1 = add_to_list(1)` → функция вызвана, список становится `[1]`.
- `res2 = add_to_list(2)` → функция вызвана снова с другим аргументом, список теперь `[1, 2]`.
- `res3 = add_to_list(1)` → аргумент `1` есть в кэше, сработает ветка `print("Из кэша")`, и вернётся ссылка на тот же изменённый список.

🔢 Итог:

```
[1, 2]
[1, 2]
Из кэша
[1, 2]
```

Все переменные указывают на один и тот же изменённый список.

💥 Почему это важно:

1️⃣ Изменяемые аргументы по умолчанию сохраняются между вызовами функции.
2️⃣ Кэширование изменяемых объектов может привести к неожиданным результатам: возвращается не неизменяемый результат, а ссылка на объект, который может изменяться позже.

🛡️ Как исправить:

1️⃣ Использовать `lst=None` и создавать новый список внутри функции:
```python
def add_to_list(val, lst=None):
if lst is None:
lst = []
lst.append(val)
return lst
```

2️⃣ Если кэшировать изменяемые объекты, лучше возвращать их копии:
```python
import copy
cache[arg] = copy.deepcopy(result)
```

Итог:

Декораторы вместе с изменяемыми аргументами — это ловушка даже для опытных программистов. Особенно, если изменяемые объекты кэшируются и потом меняются за кулисами.

@Python_Community_ru



tg-me.com/Python_Community_ru/2597
Create:
Last Update:

🐍 Задача с подвохом: Декораторы и изменяемые объекты

Условие:

Что выведет следующий код и почему?

def memoize(fn):
cache = {}
def wrapper(arg):
if arg in cache:
print("Из кэша")
return cache[arg]
else:
result = fn(arg)
cache[arg] = result
return result
return wrapper

@memoize
def add_to_list(val, lst=[]):
lst.append(val)
return lst

res1 = add_to_list(1)
res2 = add_to_list(2)
res3 = add_to_list(1)

print(res1)
print(res2)
print(res3)

Вопрос:
Что именно выведется? В чём здесь двойная ловушка?

🔍 Анализ:

Сначала кажется, что:

1. add_to_list(1) вернёт [1].
2. add_to_list(2) вернёт [2].
3. add_to_list(1) либо вызовет функцию снова, либо вернёт результат из кэша.

Но есть два подвоха:

Подвох №1: изменяемый аргумент по умолчанию

Аргумент lst=[] создаётся один раз при определении функции. Все вызовы без передачи списка будут использовать один и тот же список.

Подвох №2: кэширование по ключу

Декоратор memoize сохраняет результат в кэше по ключу arg. Но функция возвращает список, который изменяется при каждом вызове. Даже если результат берётся из кэша, вы получите ссылку на тот же список, который менялся между вызовами!

🧮 Что реально произойдёт:

- `res1 = add_to_list(1)` → функция вызвана, список становится `[1]`.
- `res2 = add_to_list(2)` → функция вызвана снова с другим аргументом, список теперь `[1, 2]`.
- `res3 = add_to_list(1)` → аргумент `1` есть в кэше, сработает ветка `print("Из кэша")`, и вернётся ссылка на тот же изменённый список.

🔢 Итог:

```
[1, 2]
[1, 2]
Из кэша
[1, 2]
```

Все переменные указывают на один и тот же изменённый список.

💥 Почему это важно:

1️⃣ Изменяемые аргументы по умолчанию сохраняются между вызовами функции.
2️⃣ Кэширование изменяемых объектов может привести к неожиданным результатам: возвращается не неизменяемый результат, а ссылка на объект, который может изменяться позже.

🛡️ Как исправить:

1️⃣ Использовать `lst=None` и создавать новый список внутри функции:
```python
def add_to_list(val, lst=None):
if lst is None:
lst = []
lst.append(val)
return lst
```

2️⃣ Если кэшировать изменяемые объекты, лучше возвращать их копии:
```python
import copy
cache[arg] = copy.deepcopy(result)
```

Итог:

Декораторы вместе с изменяемыми аргументами — это ловушка даже для опытных программистов. Особенно, если изменяемые объекты кэшируются и потом меняются за кулисами.

@Python_Community_ru

BY Python Community


Warning: Undefined variable $i in /var/www/tg-me/post.php on line 283

Share with your friend now:
tg-me.com/Python_Community_ru/2597

View MORE
Open in Telegram


Python Community Telegram | DID YOU KNOW?

Date: |

In many cases, the content resembled that of the marketplaces found on the dark web, a group of hidden websites that are popular among hackers and accessed using specific anonymising software.“We have recently been witnessing a 100 per cent-plus rise in Telegram usage by cybercriminals,” said Tal Samra, cyber threat analyst at Cyberint.The rise in nefarious activity comes as users flocked to the encrypted chat app earlier this year after changes to the privacy policy of Facebook-owned rival WhatsApp prompted many to seek out alternatives.

Find Channels On Telegram?

Telegram is an aspiring new messaging app that’s taking the world by storm. The app is free, fast, and claims to be one of the safest messengers around. It allows people to connect easily, without any boundaries.You can use channels on Telegram, which are similar to Facebook pages. If you’re wondering how to find channels on Telegram, you’re in the right place. Keep reading and you’ll find out how. Also, you’ll learn more about channels, creating channels yourself, and the difference between private and public Telegram channels.

Python Community from vn


Telegram Python Community
FROM USA